Skip to content

fix(xlsx): seat bottom-aligned cell text on the descender line - #711

Merged
developer0hye merged 1 commit into
mainfrom
fix/xlsx-bottom-align
Jul 30, 2026
Merged

fix(xlsx): seat bottom-aligned cell text on the descender line#711
developer0hye merged 1 commit into
mainfrom
fix/xlsx-bottom-align

Conversation

@developer0hye

Copy link
Copy Markdown
Owner

Summary

XLSX <alignment vertical="bottom"/> cells (Excel's default) rendered their text too high — the row's slack landed below the text. Excel's rule, measured off native GT: the last line's descent bottom rests on the row's bottom inset edge, all slack above. Two mechanisms caused the drift, both fixed for fixed-height rows only:

  1. Spill wrapper ignored vertical alignment (typst_gen_tables.rs): unwrapped cells spilling over neighbours emitted #place(.. + horizon, box(height: 1.3em, ..)) — a hardcoded centre anchor with an ambient-sized strut. The wrapper now anchors at the cell's effective vertical alignment with a pt-sized clip box and strut derived from the paragraph's own line metrics. Centre-anchored spill cells keep the byte-identical legacy emission.
  2. East Asian rows lifted the seat by the 1.3× surplus (typst_gen_text.rs): the EA line box splits Word's 1.3× hhea factor symmetrically, so align: bottom seated the line-box bottom 0.15×pitch below the real descender. A new Table IR flag seats_bottom_aligned_text_on_descender (set only by the XLSX parsers) re-seats bottom-aligned cells: bottom-edge: -descender with the removed surplus moved into par leading, keeping multi-line advance unchanged.

Auto-height rows keep the symmetric box — their intrinsic heights were GT-calibrated (#396/#411/#498) and only fixed rows have slack to distribute. DOCX/PPTX output is byte-identical (flag false; guarded by a regression test).

Related issue

Fixes #618

Testing

Visual impact

  • No rendered PDF change
  • Rendered PDF change or visual evidence added
  • Reason:

Visual audit

Visual comparison

GT Before After
GT Before After

Required inspection

  • Rendered all evidence at 150 DPI or higher
  • Stored progressive JPEG quality 86 assets with metadata stripped
  • Inspected matched region crops at full resolution
  • Ran the 5% fuzz pixel-difference sweep
  • Inventoried hairlines and border dash styles
  • Inventoried font weight, italic, and underline emphasis

Deviation audit

Check Result
Page count/order Matches GT — 4 pages on both sides, same order
Element presence Matches GT — title, table grid, header fill, data bars all present
Position/size Remaining: #710 — ht=15 auto rows print 15.00pt vs GT 14.00pt, accumulating to +40.84pt and spilling three rows to page 2; Remaining: #658 — header row prints 23.00pt vs GT 24.00pt; Remaining: #709 — EA auto rows +5.79pt/row (measured on 04_payroll_ko p1 in this audit)
Rotation/flip No deviation observed
Fill Remaining: #655 — data bars 9.2pt tall vs GT 10.0pt; bar widths and header fill colour match GT
Stroke/border Remaining: #619 — borders stroked 1pt centred on the boundary vs GT fills inside the band
Text content Matches GT — every cell string identical; the three rows on the wrong page are tracked under Position/size
Font family/weight/style Matches GT — Arial-BoldMT 14 title, Arial-BoldMT 10 header, ArialMT 10 body on both sides
Text color Matches GT — #1e2761 title, #ffffff header, #000000 body
Alignment Fixed — title descent bottom: before 91.28pt (3.67pt above GT), after 93.48pt; the 1.47pt residual is the row-1 track (#658), not seating
Line/paragraph spacing No deviation observed — all cells single-line; row-pitch deltas are tracked under Position/size
Clipping/overflow No deviation observed

Checklist

  • Commits include a Signed-off-by line
  • PR scope contains one root cause
  • Remaining visual deviations each reference an open issue

🤖 Generated with Claude Code

Excel puts a bottom-aligned cell's descent bottom on the row's bottom
inset edge, all slack above. office2pdf left the slack below the text
through two mechanisms, measured off native Excel GT:

1. The spill wrapper (unwrapped cells overflowing empty neighbours)
   hardcoded #place(.. + horizon), so the table-level align: bottom only
   reached the zero-width strut and the visible line stayed vertically
   centred — off by (rowHeight − lineHeight)/2, 2.20pt on the inventory
   title and 9.54pt on WidthsAndHeights B1. Its 1.3em clip box also
   resolved at the ambient 11pt, not the cell's font size.

2. East Asian rows carry Word's 1.3x hhea line factor (#518) with the
   surplus split symmetrically, so the line-box bottom that Typst's
   align: bottom seats on the inset edge sits 0.15 x pitch below the
   real descender — payroll row-9 numbers floated 4.41pt up where the
   font's descent is 2.4pt, putting bottom-aligned numbers above their
   centre-aligned Korean label in the same 14pt row.

The fix: in fixed-height rows only, the spill wrapper anchors at the
cell's effective vertical alignment with a pt-sized strut derived from
the paragraph's own metrics, and a new Table flag
seats_bottom_aligned_text_on_descender (set only by the XLSX parsers,
false for DOCX/PPTX whose seating is unverified against Word GT) makes
bottom-aligned cells emit bottom-edge: -descender with the removed
surplus moved into par leading, keeping multi-line advance unchanged.
Auto rows keep the symmetric box: their intrinsic heights were
calibrated against GT (#396/#411/#498) and only fixed rows have slack
to distribute.

Measured after the fix (mutool draw -F trace):
- 03_inventory_en title baseline 88.33 -> 90.53 (Excel 92.00; the
  1.47pt residual is the row-1 track, #658)
- WidthsAndHeights B1 baseline 75.01 -> 84.55 (residual is #658)
- 04_payroll_ko row-9 numbers 243.54 -> 245.54; the impossible
  bottom-above-centre inversion shrinks 3.14pt -> 1.15pt (residual is
  the EA auto-row line box, #709)

Latin fixed rows are byte-identical (hhea lineGap folds into the
ascender, so the seated bottom edge equals the old one); DOCX/PPTX are
byte-identical, guarded by a regression test.

Visual audit on 03_inventory_en p1 filed the two residual root causes
it exposed: #709 (EA auto rows +5.79pt/row) and #710 (Latin auto rows
+1pt/row).

Related: #618

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Yonghye Kwon <developer.0hye@gmail.com>
@developer0hye
developer0hye merged commit 4777c9a into main Jul 30, 2026
17 checks passed
@developer0hye
developer0hye deleted the fix/xlsx-bottom-align branch July 30, 2026 02:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

XLSX: vertical="bottom" cells leave their slack below the text, so bottom-aligned runs sit up to 12pt high

1 participant